<?php

namespace fwkit\Http;

class Client
{
    protected $referer;

    protected $headers = [];

    protected $url;

    protected $method = 'GET';

    protected $query = [];

    protected $body = [];

    protected $timeout;

    protected $sslCert;

    protected $sslKey;

    protected $error;

    protected $response;

    protected $responseText;

    protected $responseHeaders;

    protected $executed;

    protected $curl;

    // protected $additionalCurlOpts;

    protected $isMultipart = false;

    public function __construct(?string $method = null, ?string $url = null, int $timeout = 5)
    {
        $this->timeout = $timeout;
        if ($method && $url) {
            $this->initialize($method, $url);
        }
    }

    public static function request(string $method, string $url, int $timeout = 5)
    {
        return new static($method, $url, $timeout);
    }

    public static function get(string $url, int $timeout = 5)
    {
        return static::request('GET', $url, $timeout);
    }

    public static function post(string $url, int $timeout = 5)
    {
        return static::request('POST', $url, $timeout);
    }

    protected function initialize(string $method, string $url)
    {
        $this->method = strtoupper($method);
        $this->url = $url;
        $this->setHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
        $this->setHeader('Accept-Language', 'en-us,en;q=0.5');
        $this->setHeader('Accept-Encoding', 'deflate');
        $this->setHeader('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7');
        $this->setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36');
        $this->setHeader('Connection', 'close');
    }

    public function setTimeout(int $timeout)
    {
        $this->timeout = $timeout;
        return $this;
    }

    public function setHeader(string $header, string $content)
    {
        $this->headers[$header] = $content;
        return $this;
    }

    public function setReferer(string $referer)
    {
        $this->referer = $referer;
        return $this;
    }

    public function withAccessToken(string $accessToken, string $queryKey = 'access_token')
    {
        $this->query[$queryKey] = $accessToken;
        return $this;
    }

    public function withQuery($query)
    {
        if (is_array($this->query) && is_array($query)) {
            $this->query = array_merge($this->query, $query);
        } else {
            $this->query = $query;
        }

        return $this;
    }

    public function withFile(string $fieldName, string $path, ?string $mimeType = null, ?string $fileName = null)
    {
        if (!is_array($this->body)) {
            $this->body = [];
        }

        $this->body[$fieldName] = curl_file_create($path, $mimeType, $fileName);
        return $this;
    }

    public function withBody($body)
    {
        if (is_array($this->body) && is_array($body)) {
            $this->body = array_merge($this->body, $body);
        } else {
            $this->body = $body;
        }

        return $this;
    }

    public function withJson($data)
    {
        $this->body = self::jsonEncode($data);
        return $this;
    }

    public function withMultipart($data)
    {
        $this->isMultipart = true;
        return $this->withBody($data);
    }

    public function withSSLCert(string $sslCert, string $sslKey)
    {
        $this->sslCert = $sslCert;
        $this->sslKey  = $sslKey;
        return $this;
    }

    public function send(?string $saveTo = null)
    {
        $this->executed = true;
        $method = $this->method;
        $body = $this->body;
        $timeout = $this->timeout;

        if (is_array($body) && !$this->isMultipart) {
            $body = http_build_query($body);
            $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
        }

        // Initiate cURL.
        $ch = curl_init();

        // Set request type.
        if ($method === 'GET') {
            curl_setopt($ch, CURLOPT_HTTPGET, true);
        } elseif ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            if ($body) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
            }
        } elseif ($method === 'PUT') {
            curl_setopt($ch, CURLOPT_PUT, true);
        } else {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        }

        // Grab query string.
        $url = $this->url;
        if ($this->query) {
            $query = is_array($this->query) ? http_build_query($this->query) : strval($this->query);
            $url .= (strpos($url, '?') === false ? '?' : '&') . $query;
        }

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        // Set additional headers.
        $headers = [];
        foreach ($this->headers as $name => $val) {
            $headers[] = $name.': '.$val;
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        // Do stuff it it's HTTPS/SSL.
        if (substr($url, 0, 8) == 'https://') {
            $port = 443;

            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        } else {
            $port = 80;
        }

        // if (!empty($this->additonalCurlOpts)) {
        //     foreach ($this->additionalCurlOpts as $option => $value) {
        //         curl_setopt($ch, $option, $value);
        //     }
        // }
        // Build and set URL.
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_PORT, $port);

        if ($this->sslCert && $this->sslKey) {
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');

            curl_setopt($ch, CURLOPT_SSLCERT, $this->sslCert);
            curl_setopt($ch, CURLOPT_SSLKEY, $this->sslKey);
        }

        if ($this->referer) {
            curl_setopt($ch, CURLOPT_REFERER, $this->referer);
        }

        // Execute!
        if ($saveTo === null) {
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

            $data = curl_exec($ch);
            $headers = curl_getinfo($ch);
            $error = curl_error($ch);
            curl_close($ch);

            // Handle an error.
            if ($error) throw new \Exception($error);

            $this->response = ['responseText' => $data] + $headers;
            $this->responseHeaders = $headers;
            $this->responseText = $data;
        } else {
            $fh = fopen($saveTo, 'w');
            curl_setopt($ch, CURLOPT_FILE, $fh);
            curl_exec($ch);
            curl_close($ch);
            fclose($fh);
        }

        return $this;
    }

    public function download(string $saveTo)
    {
        return $this->send($saveTo);
    }

    public function getError()
    {
        return $this->error;
    }

    public function getResponse()
    {
        if (!$this->executed) {
            $this->send();
        }

        return $this->response;
    }

    public function getText()
    {
        if (!$this->executed) {
            $this->send();
        }

        return $this->responseText;
    }

    public function getJson()
    {
        if (!$this->executed) {
            $this->send();
        }

        return $this->responseText ? @static::jsonDecode($this->responseText) : [];
    }

    public function getResponseHeaders()
    {
        if (!$this->executed) {
            $this->send();
        }

        return $this->responseHeaders;
    }

    public function getResponseHeader(string $header)
    {
        if (!$this->executed) {
            $this->send();
        }

        $headers = $this->responseHeaders;
        if (array_key_exists($header, $headers)) {
            return $headers[$header];
        }
    }

    protected static function jsonEncode(array $data = array())
    {
        return json_encode($data, JSON_UNESCAPED_UNICODE);
    }

    protected static function jsonDecode(string $data)
    {
        return json_decode($data, true);
    }
}
